/**
 * TODO LIST
 * Make a game.
 * Use smart pointers.
 **/

#include "DataDef.h"

#include "GraphicsModule.h"
#include "EffectManager.h"
#include "Water.h"

#include "Camera.h"
#include "Util.h"
#include "Gaussian.h"
#include "TextureManager.h"
#include "ModelMesh.h"

#include "AbstractLightModels.h"
#include "DeferredLightingDef.h"

#include "PostProcess.h"
#include "DeadReck.h"

#include <vector>
#include <cstdlib>
#include <time.h>
#include <sstream>
#include <string>

using namespace std;

WORD int_AppRunning = 1;

FLOAT fltCnt = 0;

LPDIRECT3DSURFACE9 pBackBuffer;

// Meshes
Water *water;
vector<ModelMesh *> meshes;
ModelMesh *skyDome;
ModelMesh *coneLight;
ModelMesh *sphereLight;
ModelMesh *quad;
ModelMesh *player;
ModelMesh *playerProxy;

IDirect3DVertexDeclaration9* ppDecl = NULL;

GbufferEffect *gbEffect;
ShadowEffect *shEffect;
PointLightEffect *plEffect;
SpotLightEffect *slEffect;
DirectionalLightEffect *dlEffect;
PostProcess *ppEffect;

TextureManager *texMgr;
EffectManager *effMgr;
Util *util;
Camera *camera;
// Dead reckoning simulator
Network::DeadReck *dr;
// Lights 
vector<SpotLight> spotLights;
vector<PointLight> pointLights;

LPDIRECTINPUT8 pDI;
LPDIRECTINPUTDEVICE8 pMouse;
LRESULT CALLBACK handleMessages(HWND han_Wind, UINT uint_Message, WPARAM parameter1, LPARAM parameter2);

FLOAT focalLength = 15.0f;
FLOAT focalDistance = 10.0f;
bool keyStates[10] = {false, false, false, false, false, false, false, false, false, false};
bool spotLightsOn = true, pointLightsOn = true, DOFOn = true, HDROn = true, textOn = true, dirLghtOn = true, waterPhys = true, defOn = true;
const FLOAT timestep = 0.01f;

void printInstructions(wstringstream &ss)
{
	if (!textOn)
	{
		return;
	}
	wstring wstr;
	ss<<"Instructions:";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"Camera^Movement:";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^W:^Foward"; 
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^S:^Back";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^A:^Strafe^Left";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^D:^Strafe^Right";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^mouse:^Look^around";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"Bot^movement(Red=Player,Gray=Proxy):";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^I:^Apply^foward^force";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^K:^Apply^break^force";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^J:^Rotate^Left";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^L:^Rotate^Right";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"Depth^of^field:";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^Focal^length.^"<<focalLength;
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^Focal^distance.^"<<focalDistance;
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^1:^Increase^focal^length.";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^2:^Decrease^focal^length.";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^3:^Increase^focal^point.";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"^^4:^Decrease^focal^point.";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"5:^Toggle^Spot^lights.^"<<((spotLightsOn) ? "true" : "false");
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"6:^Toggle^Point^lights.^"<<((pointLightsOn) ? "true" : "false");
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"7:^Toggle^DOF.^"<<((DOFOn) ? "true" : "false");
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"8:^Toggle^HDR.^"<<((HDROn) ? "true" : "false");
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"9:^Toggle^directional^light.^"<<((dirLghtOn) ? "true" : "false");
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"0:^Toggle^help.^"<<((textOn) ? "true" : "false");
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"C:^Reset^camera.^";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"V:^Center^camera^on^dead^reckoning^bots.^";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"C:^Water^physics.^";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	ss<<"P:^Toggle^Phong^or^Deferred^lighting.^";
	WRITE_AND_CLEAR_TXT(ss, wstr);
	util->renderText();
}

bool keyHitOnce(char key, bool &keyHit)
{
	if( ::GetAsyncKeyState(key) & 0x8000f && keyHit == false)
	{
		keyHit = true;
		return true;
	}
	else if(!::GetAsyncKeyState(key) && keyHit == true)
	{
		keyHit = false;
	}
	return false;
}

void updateTimeSensitiveOptions()
{
	if(::GetAsyncKeyState('1') & 0x8000f)
	{
		focalLength += 0.1f;
	}
	if(::GetAsyncKeyState('2') & 0x8000f)
	{
		focalLength = (focalLength < 0.1f) ? 0.0f : focalLength - 0.1f;
	}
	if(::GetAsyncKeyState('3') & 0x8000f)
	{
		focalDistance += 0.1f;
	}
	if(::GetAsyncKeyState('4') & 0x8000f)
	{
		focalDistance =  (focalDistance < 0.1f) ? 0.0f : focalDistance - 0.1f;
	}
	if(keyHitOnce('5', keyStates[0]))
	{
		spotLightsOn ^= true;
	}
	if(keyHitOnce('6', keyStates[1]))
	{
		pointLightsOn ^= true;
	}
	if(keyHitOnce('7', keyStates[2]))
	{
		DOFOn ^= true;
	}
	if(keyHitOnce('8', keyStates[3]))
	{
		HDROn ^= true;
	}
	if(keyHitOnce('9', keyStates[4]))
	{
		dirLghtOn ^= true;
	}
	if(keyHitOnce('0', keyStates[5]))
	{
		textOn ^= true;
	}
	if(keyHitOnce('V', keyStates[6]))
	{
		camera->resetCamera();
		camera->setPosition(-10.0f, 10.0f, -10.0f);
		camera->pitch(25.0f * (D3DX_PI / 180.0f));
		camera->yaw(45.0f * (D3DX_PI / 180.0f));
	}
	if(keyHitOnce('B', keyStates[7]))
	{
		camera->resetCamera();
		camera->setPosition(0.0f, 4.0f, -2.0f);
		camera->pitch(60.0f * (D3DX_PI / 180.0f));
	}
	if(keyHitOnce('C', keyStates[8]))
	{
		waterPhys ^= true;
	}
	if(keyHitOnce('P', keyStates[9]))
	{
		defOn ^= true;
	}
}

void renderPhong(LPDIRECT3DDEVICE9 p_dx_Device)
{
	
	HRESULT hr;
	UINT numPasses;

	hr = p_dx_Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
	// for the dof
	hr = p_dx_Device->SetRenderTarget(0, *texMgr->getSurface(L"depthMap"));
	hr = p_dx_Device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0,0,0,0), 1.0f, 0);
	hr = effMgr->setupEffect(DEPTH_FX);
	hr = effMgr->getSetEffect()->SetMatrix("View", &(camera->getViewMatrix()));
	hr = effMgr->getSetEffect()->SetMatrix("Projection", &(camera->getProjMatrix()));
	p_dx_Device->BeginScene();
	effMgr->getSetEffect()->Begin(NULL, 0);
	vector<ModelMesh *>::const_iterator itr;
	for(itr = meshes.begin();  itr != meshes.end(); itr++) 
	{
		ModelMesh *mesh = (ModelMesh *)*itr;
		effMgr->getSetEffect()->SetMatrix("World", &mesh->getWorld());
		effMgr->getSetEffect()->BeginPass(0);
		mesh->getMesh()->DrawSubset(0);
		effMgr->getSetEffect()->EndPass();
	}
	effMgr->getSetEffect()->End();
	p_dx_Device->EndScene();

	p_dx_Device->SetRenderTarget(0, *texMgr->getSurface(L"buf0"));
	p_dx_Device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0,0,0,0), 1.0f, 0);
	if (dirLghtOn)
	{
		SHOW_ERROR_MSG(effMgr->setupEffect(DIFF_FX), L"HLSL operation error.");
		hr = effMgr->getSetEffect()->SetMatrix("ViewMatrix", &camera->getViewMatrix());
		hr = effMgr->getSetEffect()->SetMatrix("Projection", &camera->getProjMatrix());
		hr = effMgr->getSetEffect()->SetVector("AmbientMtrl", &D3DXVECTOR4(0.1f, 0.1f, 0.1f, 1.0f));
		hr = effMgr->getSetEffect()->SetVector("DiffuseMtrl", &D3DXVECTOR4(0.8f, 0.8f, 0.8f, 1.0f));
		hr = effMgr->getSetEffect()->SetVector("LightDirection", &D3DXVECTOR4(cos(fltCnt * 0.5), 0.5f, sin(fltCnt * 0.5), 0));
		hr = effMgr->getSetEffect()->SetVector("CameraPosition", &camera->getPosition4());
		hr = effMgr->getSetEffect()->SetMatrix("InvertViewProjection", &camera->getInvViewProjMatrix());

		hr = p_dx_Device->BeginScene();
		hr = effMgr->getSetEffect()->Begin(&numPasses, 0);
		hr = effMgr->getSetEffect()->SetMatrix("World", &(skyDome->getWorld()));
		hr = effMgr->getSetEffect()->SetTexture("Texture", texMgr->getTexture(SKY_TEX));
		hr = effMgr->getSetEffect()->BeginPass(0);
		skyDome->getMesh()->DrawSubset(0);
		hr = effMgr->getSetEffect()->EndPass();

		vector<ModelMesh *>::const_iterator itr;
		for(itr = meshes.begin();  itr != meshes.end(); itr++) 
		{
			ModelMesh *mesh = (ModelMesh *)*itr;
			hr = effMgr->getSetEffect()->SetMatrix("World", &(mesh->getWorld()));
			hr = effMgr->getSetEffect()->SetTexture("Texture", texMgr->getTexture(mesh->getTextureName()));
			if (mesh->getSpecTextureName() != NULL)
			{
				hr = effMgr->getSetEffect()->SetTexture("SpecTexture", texMgr->getTexture(mesh->getSpecTextureName()));
			}
			hr = effMgr->getSetEffect()->BeginPass(0);
			hr = mesh->getMesh()->DrawSubset(0);
			hr = effMgr->getSetEffect()->EndPass();
			hr = effMgr->getSetEffect()->SetTexture("SpecTexture", NULL);
			hr = effMgr->getSetEffect()->SetTexture("Texture", NULL);
		}
		effMgr->getSetEffect()->End();
		p_dx_Device->EndScene();
	}
}
void renderDeferred(LPDIRECT3DDEVICE9 p_dx_Device)
{
	// Start deferred lighting
	gbEffect->clearGBuffer(quad, pBackBuffer);
	gbEffect->renderGBuffer(camera, meshes, pBackBuffer);
	// Clear the light map and begin to accumulate all lights into lightMap surface
	gbEffect->start(pBackBuffer);
	// Render spot lights (Shadow casters)
	if (spotLightsOn)
	{
		for (vector<SpotLight>::const_iterator itr = spotLights.begin(); itr < spotLights.end(); ++itr)
		{
			SpotLight light = static_cast<SpotLight>(*itr);
			shEffect->renderDepthBuffer(camera, &light, meshes, pBackBuffer);
			slEffect->renderSpotLight(camera, &light, coneLight, pBackBuffer);
		}
	}

	// Render point lights.
	if (pointLightsOn)
	{
		for (vector<PointLight>::const_iterator itr = pointLights.begin(); itr < pointLights.end(); ++itr)
		{
			PointLight light = static_cast<PointLight>(*itr);
			plEffect->renderPointLight(camera, &light, sphereLight);

		}
	}
	if (dirLghtOn)
	{
		// Render global directional light
		dlEffect->renderDirectionalLight(camera, quad, D3DXVECTOR4(-cos(fltCnt * 0.5f), -0.3f, -sin(fltCnt * 0.5f), 0.0f), D3DXVECTOR4(0.2f, 0.2f, 0.2f, 3.0f), pBackBuffer);
	}

	// Finished lighting
	p_dx_Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);

	// Combine color and light
	gbEffect->combineGBuffer(quad, *texMgr->getSurface(L"buf0"), pBackBuffer); //<-- final output is in buf0 surface
}

void render(LPDIRECT3DDEVICE9 p_dx_Device)
{
	std::wstringstream ss;
	std::wstring  wstr;

	UINT numPasses = NULL;
	HRESULT hr = NULL;
	FLOAT cameraToCenter = 0.0f;

	// Setup spotlights
	spotLights[0].setPosition(cos(fltCnt) * 10.0f, 8.0f, sin(fltCnt) * 10.0f);
	spotLights[0].setTarget(0.0f, -4.0f, 0.0f);
	spotLights[0].setRadius(20.0f);
	spotLights[0].setColor(1.0f, 0.7f, 0.5f);
	spotLights[0].setIntensity(2.0f);
	spotLights[0].setDecay(10.0f);

	spotLights[1].setPosition(-cos(fltCnt) * 10.0f, 8.0f, -sin(fltCnt) * 10.0f);
	spotLights[1].setTarget(0.0f, -4.0f, 0.0f);
	spotLights[1].setRadius(30.0f);
	spotLights[1].setColor(0.5f, 0.7f, 1.0f);
	spotLights[1].setIntensity(2.0f);
	spotLights[1].setDecay(10.0f);

	// Setup pointlights
	pointLights[0].setPosition(-cos(fltCnt * 4) * 0.5f, 1.1f, -sin(fltCnt * 4) * 0.5f);
	pointLights[0].setRadius(0.7f);
	pointLights[0].setColor(1.0f, 0.0f, 0.0f);
	pointLights[0].setIntensity(5.0f);

	pointLights[1].setPosition(cos(fltCnt * 4) * 0.5f, 1.05f, sin(fltCnt * 4) * 0.5f);
	pointLights[1].setRadius(0.7f);
	pointLights[1].setColor(0.0f, 1.0f, 0.0f);
	pointLights[1].setIntensity(5.0f);

	pointLights[2].setPosition(0.0f, 1.2f, 0.0f);
	pointLights[2].setRadius(1.0f + cos(fltCnt * 10.0f) * 0.2f);
	pointLights[2].setColor(1.0f, 1.0f, 1.0f);
	pointLights[2].setIntensity(2.0f);

	pointLights[3].setPosition(sin(fltCnt * 0.3f) * 50.0f, 10.0f, cos(fltCnt * 0.3f) * 50.0f);
	pointLights[3].setRadius(50.0f);
	pointLights[3].setColor(1.0f, 1.0f, 1.0f);
	pointLights[3].setIntensity(20.0f);

	pointLights[4].setPosition(5.0f, 2.0f, 5.0f);
	pointLights[4].setRadius(10.0f + cos(fltCnt * 5.0f));
	pointLights[4].setColor(1.0f, 1.0f, 1.0f);
	pointLights[4].setIntensity(3.0f);

	FLOAT srcXpos = dr->sourcePosition->x / Network::MOVE_SCALE * 0.65f;
	FLOAT srcYpos = dr->sourcePosition->y / Network::MOVE_SCALE * 0.65f;

	FLOAT remXpos = dr->remotePosition->x / Network::MOVE_SCALE * 0.65f;
	FLOAT remYpos = dr->remotePosition->y / Network::MOVE_SCALE * 0.65f;

	// Update player positions
	D3DXMATRIX matPlayer;
	D3DXMatrixTranslation(&matPlayer, srcXpos, 0.7f, srcYpos);
	player->setWorld(matPlayer);

	D3DXMatrixIdentity(&matPlayer);
	D3DXMatrixTranslation(&matPlayer, remXpos, 0.7f, remYpos);
	playerProxy->setWorld(matPlayer);

	ss<<"Real^("<<dr->sourcePosition->x<<",^"<<dr->sourcePosition->y<<")";
	WRITE_AND_CLEAR_TXT(ss, wstr);

	ss<<"Proxy^("<<dr->remotePosition->x<<",^"<<dr->remotePosition->y<<")";
	WRITE_AND_CLEAR_TXT(ss, wstr);

	// Clear the back buffer and render targets
	p_dx_Device->SetRenderTarget(0, pBackBuffer);
	p_dx_Device->SetRenderTarget(1, NULL);
	p_dx_Device->SetRenderTarget(2, NULL);
	p_dx_Device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,0), 1.0f, 0);

	if (defOn && TextureManager::SUPPORT_MRT)
	{
		renderDeferred(p_dx_Device);
	}
	else
	{
		// For gfx without MRT
		renderPhong(p_dx_Device);

	}

	// Start post processing
	if (HDROn)
	{
		// Blur the scene
		ppEffect->blurTexture(
			texMgr->getTexture(L"buf0"), // input texture
			*texMgr->getSurface(L"buf1"), // output surface
			quad, pBackBuffer);
		// Apply bloom
		p_dx_Device->SetRenderTarget(0, *texMgr->getSurface(L"buf0"));
		ppEffect->bloom(
			texMgr->getTexture(L"buf0"), // normal
			texMgr->getTexture(L"buf1"), // blurred
			quad, pBackBuffer);
	}
	if (DOFOn)
	{
		// Blur the scene
		ppEffect->blurTexture(
			texMgr->getTexture(L"buf0"),
			*texMgr->getSurface(L"buf1"),
			quad, pBackBuffer);

			

		// Apply DOF
		p_dx_Device->SetRenderTarget(0, *texMgr->getSurface(L"buf0"));
		ppEffect->depthOfField(
			texMgr->getTexture(L"buf0"),
			texMgr->getTexture(L"buf1"),
			texMgr->getTexture(L"depthMap"),
			camera,
			quad,
			focalDistance,
			focalLength, pBackBuffer);
	}

	// Render the skydome unlit and combine with the postprocess effects
	p_dx_Device->SetRenderTarget(0, *texMgr->getSurface(L"buf1"));
	p_dx_Device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
	if (dirLghtOn)
	{	
		D3DXMATRIX rotate;
		D3DXMatrixRotationY(&rotate, fltCnt * 0.5f);
		skyDome->setWorld(rotate);

		p_dx_Device->SetTransform(D3DTS_VIEW, &camera->getViewMatrix());
		p_dx_Device->SetTransform(D3DTS_PROJECTION, &camera->getProjMatrix());

		p_dx_Device->SetRenderState(D3DRS_LIGHTING, true); 
		p_dx_Device->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL);
		p_dx_Device->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(255, 255, 255));

		D3DMATERIAL9 mtrl;
		ZeroMemory(&mtrl, sizeof(mtrl));
		mtrl.Ambient.r = mtrl.Ambient.g = mtrl.Ambient.b = mtrl.Ambient.a = 1.0f;
		mtrl.Diffuse.r = mtrl.Diffuse.g = mtrl.Diffuse.b = mtrl.Diffuse.a = 1.0f;
		p_dx_Device->SetMaterial(&mtrl);

		p_dx_Device->BeginScene();
		p_dx_Device->SetTexture(0, texMgr->getTexture(SKY_TEX));
		p_dx_Device->SetTransform(D3DTS_WORLD, &skyDome->getWorld());
		skyDome->getMesh()->DrawSubset(0);
		p_dx_Device->SetTexture(0, texMgr->getTexture(PLAYER_TEX));
		p_dx_Device->SetTransform(D3DTS_WORLD, &player->getWorld());
		player->getMesh()->DrawSubset(0);
		p_dx_Device->SetTexture(0, texMgr->getTexture(PROXY_TEX));
		p_dx_Device->SetTransform(D3DTS_WORLD, &playerProxy->getWorld());
		playerProxy->getMesh()->DrawSubset(0);

		// Mask the dome with the other meshes
		p_dx_Device->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(0, 0, 0));
		vector<ModelMesh *>::const_iterator itr;
		for(itr = meshes.begin();  itr != meshes.end(); itr++) 
		{
			ModelMesh *mesh = (ModelMesh *)*itr;
			if (mesh == player || mesh == playerProxy)
			{
				continue;
			}
			p_dx_Device->SetTexture(0, texMgr->getTexture(mesh->getTextureName()));
			p_dx_Device->SetTransform(D3DTS_WORLD, &mesh->getWorld());
			mesh->getMesh()->DrawSubset(0);
		}
		p_dx_Device->EndScene();

		D3DXMATRIX identity;
		D3DXMatrixIdentity(&identity);
		p_dx_Device->SetTransform(D3DTS_WORLD, &identity);
		p_dx_Device->SetTransform(D3DTS_VIEW, &identity);
		p_dx_Device->SetTransform(D3DTS_PROJECTION, &identity);
	}

	// Combine sky dome and post process effects to produce the final effect. Phew...
	p_dx_Device->SetRenderTarget(0, pBackBuffer);
	effMgr->setupEffect(DRAW_QUAD_FX);
	effMgr->getSetEffect()->SetTexture("outputTexture", texMgr->getTexture(L"buf0"));
	effMgr->getSetEffect()->SetTexture("outputTexture2", texMgr->getTexture(L"buf1"));

	p_dx_Device->BeginScene();
	effMgr->getSetEffect()->Begin(&numPasses, 0);
	effMgr->getSetEffect()->BeginPass(0);
	quad->getMesh()->DrawSubset(0);
	effMgr->getSetEffect()->EndPass();
	effMgr->getSetEffect()->End();
	p_dx_Device->EndScene();

	printInstructions(ss);

	//for (int i = 0; i < 50000000; ++i);
}

void setupTextures (LPDIRECT3DDEVICE9 p_dx_Device)
{
	texMgr->loadTexture(BALL_TEX);
	texMgr->loadTexture(BALL_TEX_SPEC);
	texMgr->loadTexture(WATER_TEX);
	texMgr->loadTexture(WATER_TEX_SPEC);
	texMgr->loadTexture(LIGHT_TEX);
	texMgr->loadTexture(MOUNTAIN_TEX);
	texMgr->loadTexture(SKY_TEX);
	texMgr->loadTexture(PLAYER_TEX);
	texMgr->loadTexture(PLAYER_TEX_SPEC);
	texMgr->loadTexture(PROXY_TEX);
	texMgr->loadTexture(PROXY_TEX_SPEC);

	// Buffers for postprocessing
	texMgr->makeRenderTarget(L"buf0", TextureManager::RT_FORMAT);
	texMgr->makeRenderTarget(L"buf1", TextureManager::RT_FORMAT);

	// Keep a pointer to the back buffer
	p_dx_Device->GetRenderTarget(0, &pBackBuffer);
}

HWND newWindow(LPCTSTR str_Title,int int_XPos, int int_YPos, int int_Width, int int_Height)
{
	WNDCLASSEX wnd_Structure;
	wnd_Structure.cbSize = sizeof(WNDCLASSEX);
	wnd_Structure.style = CS_HREDRAW | CS_VREDRAW;
	wnd_Structure.lpfnWndProc = handleMessages; // call back for handling loop messages
	wnd_Structure.cbClsExtra = 0;
	wnd_Structure.cbWndExtra = 0;
	wnd_Structure.hInstance = GetModuleHandle(NULL);
	wnd_Structure.hIcon = NULL;
	wnd_Structure.hCursor = NULL;
	wnd_Structure.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
	wnd_Structure.lpszMenuName = NULL;
	wnd_Structure.lpszClassName = L"WindowClassName";
	wnd_Structure.hIconSm = LoadIcon(NULL,IDI_APPLICATION);

	RegisterClassEx(&wnd_Structure);

	return CreateWindowEx(
		WS_EX_CONTROLPARENT, 
		L"WindowClassName", 
		str_Title, 
		WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE, 
		int_XPos, 
		int_YPos, 
		int_Width, 
		int_Height, 
		NULL, 
		NULL, 
		GetModuleHandle(NULL), 
		NULL);
}

// Pieces borrowd from Fixed time step Gaffer on games
void runLoop(LPDIRECT3DDEVICE9 p_dx_Device, HWND han_Window)
{
	UINT t = 0;
	FLOAT accumulator = 0.0f;
	FLOAT absoluteTime = 0.0f;
	HRESULT hr;

	DIMOUSESTATE2 dims2; 
	ZeroMemory(&dims2, sizeof(dims2));

	MSG msg_Message;
	while (int_AppRunning) 
	{
		// update absolute time
		FLOAT newTime = util->time();
		FLOAT deltaTime = newTime - absoluteTime;

		if (deltaTime <= 0.0f)
		{
			continue;
		}

		absoluteTime = newTime;
		accumulator += deltaTime;

		// update discrete time
		while (accumulator >= timestep)
		{
			hr = pMouse->GetDeviceState(sizeof(DIMOUSESTATE2), &dims2);

			if(FAILED(hr)) 
			{
				hr = pMouse->Acquire();
				while(hr == DIERR_INPUTLOST)
				{
					hr = pMouse->Acquire();
				}
			}

			const FLOAT mouseSpeed = 0.005f;
			camera->yaw(dims2.lX * mouseSpeed);
			camera->pitch(dims2.lY * mouseSpeed);
			camera->updateControls();

			updateTimeSensitiveOptions();


			dr->update(timestep);
			fltCnt += 0.01f;
			// advance discrete time
			accumulator -= timestep;
			t++;
		}

		if (waterPhys)
		{
			water->UpdateWater();

			if (t % 5 == 0)
			{
				FLOAT srcXpos = dr->sourcePosition->x / Network::MOVE_SCALE * 0.65f;
				FLOAT srcYpos = dr->sourcePosition->y / Network::MOVE_SCALE * 0.65f;
				FLOAT remXpos = dr->remotePosition->x / Network::MOVE_SCALE * 0.65f;
				FLOAT remYpos = dr->remotePosition->y / Network::MOVE_SCALE * 0.65f;

				FLOAT xDimplePos = cos(fltCnt) * 0.25f;
				FLOAT zDimplePos = sin(fltCnt) * 0.25f;
				water->DimpleWater(
					(0.65f) * srcXpos * static_cast<FLOAT>(water->GetDepth()) + water->GetDepth()/2,
					(0.65f) * srcYpos * static_cast<FLOAT>(water->GetWidth()) + water->GetWidth()/2,
					0.7f, 0.2f);
			}
		}

		// render view
		render(p_dx_Device);
		p_dx_Device->Present(NULL,NULL,NULL,NULL);

		// update display
		if(PeekMessage(&msg_Message, han_Window, 0, 0, PM_REMOVE))
		{
			DispatchMessage(&msg_Message);
		}

		Sleep(0);
	}
}

HRESULT setupMouseInput(HWND hWnd)
{
	HRESULT hr;

	hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (VOID**)&pDI, NULL );
	if(FAILED(hr))
		return hr;

	hr = pDI->CreateDevice( GUID_SysMouse, &pMouse, NULL );
	if(FAILED(hr))
		return hr;

	hr = pMouse->SetDataFormat( &c_dfDIMouse2 );
	if(FAILED(hr))
		return hr;

	hr = pMouse->SetCooperativeLevel( hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND );
	if( FAILED(hr) )
		return hr;

	DIPROPDWORD dipdw;
	dipdw.diph.dwSize		= sizeof(DIPROPDWORD);
	dipdw.diph.dwHeaderSize	= sizeof(DIPROPHEADER);
	dipdw.diph.dwObj		= 0;
	dipdw.diph.dwHow		= DIPH_DEVICE;
	dipdw.dwData			= 16; // Arbitrary buffer size

	if(FAILED(hr = pMouse->SetProperty( DIPROP_BUFFERSIZE, &dipdw.diph)))
	{
		return hr;
	}

	pMouse->Acquire();
	return hr;
}

void cleanUp()
{
	delete gbEffect;
	delete plEffect;
	delete shEffect;
	delete slEffect;
	delete dlEffect;
	delete ppEffect;

	for(vector<ModelMesh *>::const_iterator itr = meshes.begin(); itr != meshes.end(); itr++) 
	{
		ModelMesh *mTex = (ModelMesh *)*itr;
		delete mTex;
	}
	delete skyDome;
	delete quad;
	delete coneLight;
	delete sphereLight;

	delete effMgr;
	pBackBuffer->Release();
	ppDecl->Release();
	delete util;
	delete texMgr;

	meshes.clear();
	pMouse->Release();
	pDI->Release();

	delete dr;

	spotLights.clear();
	pointLights.clear();
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPreviousInstance,LPSTR lpcmdline,int nCmdShow)
{
	HRESULT hr;
	// Setup up simple dead reckoning simulation
	dr = new Network::DeadReck();

	// Setup the windows
	HWND han_Window = newWindow(L"Dead Reckoning", 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

	// Start up DirectX
	GraphicsModule *module = new GraphicsModule;
	if (!module->initGraphics(han_Window))
	{
		delete module;
		DestroyWindow(han_Window);
		return 0;
	}
	UINT currentMem = module->p_Device->GetAvailableTextureMem();
	// Setup mouse movement
	setupMouseInput(han_Window);

	// Create a new camera
	camera = new Camera();
	camera->setPosition(-10.0f, 10.0f, -10.0f);
	camera->pitch(25.0f * (D3DX_PI / 180.0f));
	camera->yaw(45.0f * (D3DX_PI / 180.0f));

	//camera->setPosition(0.0f, 20.0f, 0.0f);
	//camera->pitch(90 * (D3DX_PI / 180.0f));

	spotLights.push_back(SpotLight());
	spotLights.push_back(SpotLight());

	pointLights.push_back(PointLight());
	pointLights.push_back(PointLight());
	pointLights.push_back(PointLight());
	pointLights.push_back(PointLight());
	pointLights.push_back(PointLight());

	// Create utility class
	util = new Util(module->p_Device);

	// Texture manager
	texMgr = new TextureManager(module->p_Device);

	// Setup the vertex declaration
	ppDecl = 0;
	hr = module->p_Device->CreateVertexDeclaration(vertexInfo::VERTEX_FORMAT, &ppDecl);
	if( FAILED(hr) ) MessageBox(NULL,DXGetErrorDescriptionW(hr),L"Error setting vertex format.",MB_OK);
	hr = module->p_Device->SetVertexDeclaration(ppDecl);
	if( FAILED(hr) ) MessageBox(NULL,DXGetErrorDescriptionW(hr),L"Error setting vertex declaration.",MB_OK);

	// Setup frame buffers and textures
	setupTextures(module->p_Device);

	// Load up the HLSL effects
	effMgr = new EffectManager;
	effMgr->loadEffectFile(module->p_Device, GBUFFER_COMBINE_FX);
	effMgr->loadEffectFile(module->p_Device, GBUFFER_CLEAR_FX);
	effMgr->loadEffectFile(module->p_Device, DEF_DIRECTIONAL_LIGHT_FX);
	effMgr->loadEffectFile(module->p_Device, GBUFFER_RENDER_FX);
	effMgr->loadEffectFile(module->p_Device, DEF_POINT_LIGHT_FX);
	effMgr->loadEffectFile(module->p_Device, DEF_SPOT_LIGHT_FX);
	effMgr->loadEffectFile(module->p_Device, SHADOW_RENDER_FX);
	effMgr->loadEffectFile(module->p_Device, BLUR_FX);
	effMgr->loadEffectFile(module->p_Device, GLOW_FX);
	effMgr->loadEffectFile(module->p_Device, DOF_FX);
	effMgr->loadEffectFile(module->p_Device, DRAW_QUAD_FX);
	effMgr->loadEffectFile(module->p_Device, DIFF_FX);
	effMgr->loadEffectFile(module->p_Device, DEPTH_FX);

	// Create meshes for the scene
	D3DXMATRIX matWorld, matTransWater, matScaleWater;
	D3DXMatrixIdentity(&matWorld);
	D3DXMatrixScaling(&matScaleWater, 2.0f, 1.0f, 2.0f);
	D3DXMatrixTranslation(&matTransWater, 0.0f, 0.5f, 0.0f);

	water = new Water(module->p_Device, 40, 40);
	meshes.push_back(new ModelMesh(
		water->getMesh(), 
		(matWorld * matScaleWater * matTransWater), 
		WATER_TEX, WATER_TEX_SPEC));

	meshes.push_back(new ModelMesh(
		ModelMesh::loadMesh(module->p_Device, MAIN_STAGE_X),
		matWorld,
		BALL_TEX, BALL_TEX_SPEC));

	meshes.push_back(new ModelMesh(
		ModelMesh::loadMesh(module->p_Device, MOUNTAINS_X),
		matWorld,
		MOUNTAIN_TEX, MOUNTAIN_TEX));

	D3DXMATRIX matPlayer;
	D3DXMatrixTranslation(&matPlayer, dr->sourcePosition->x, 2.0f, dr->sourcePosition->y);
	player = new ModelMesh(
		ModelMesh::loadMesh(module->p_Device, BOT_X),
		matPlayer,
		PLAYER_TEX, PLAYER_TEX_SPEC);
	meshes.push_back(player);

	D3DXMatrixIdentity(&matPlayer);
	D3DXMatrixTranslation(&matPlayer, dr->remotePosition->x, 2.0f, dr->remotePosition->y);
	playerProxy = new ModelMesh(
		ModelMesh::loadMesh(module->p_Device,BOT_X),
		matPlayer,
		PROXY_TEX, PROXY_TEX_SPEC);
	meshes.push_back(playerProxy);

	skyDome = new ModelMesh(ModelMesh::loadMesh(module->p_Device, SKY_DOME_X), matWorld, SKY_TEX);

	// Create meshes for deferred lighting and post processing
	quad = new ModelMesh(util->generateQuad(), matWorld, NULL);
	coneLight = new ModelMesh(ModelMesh::loadMesh(module->p_Device, CONE_LIGHT_X), matWorld, NULL);
	sphereLight  = new ModelMesh(ModelMesh::loadMesh(module->p_Device, SPHERE_LIGHT_X), matWorld, NULL);

	// Create classes for HLSL logic encapsulation
	gbEffect = new GbufferEffect(module->p_Device, effMgr, texMgr);
	plEffect = new PointLightEffect(module->p_Device, effMgr, texMgr);
	shEffect = new ShadowEffect(module->p_Device, effMgr, texMgr);
	slEffect = new SpotLightEffect(module->p_Device, effMgr, texMgr);
	dlEffect = new DirectionalLightEffect(module->p_Device, effMgr, texMgr);
	ppEffect = new PostProcess(module->p_Device, effMgr, texMgr);

	currentMem = module->p_Device->GetAvailableTextureMem();
	// begin updating and rendering loop
	runLoop(module->p_Device, han_Window);
	currentMem = module->p_Device->GetAvailableTextureMem();
	cleanUp();

	delete module;
	DestroyWindow(han_Window);
	return 0;
}

LRESULT CALLBACK handleMessages(HWND han_Wind, UINT uint_Message, WPARAM parameter1, LPARAM parameter2)
{
	switch(uint_Message)
	{
	case WM_KEYDOWN:
		{
			if (parameter1 == VK_ESCAPE)
			{
				int_AppRunning = 0;
			}
			break;
		}
		break;
	case WM_CLOSE:
		int_AppRunning = 0;
		break;
	case WM_DESTROY:
		break;
	}
	return DefWindowProc(han_Wind, uint_Message, parameter1, parameter2);
}